package fons.navigator.views.island;

import java.awt.Color;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.swing.JOptionPane;

import org.apache.log4j.Logger;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import edu.umd.cs.piccolo.PCamera;
import edu.umd.cs.piccolo.PNode;
import edu.umd.cs.piccolo.event.PBasicInputEventHandler;
import edu.umd.cs.piccolo.event.PInputEvent;
import fons.model.ontology.Dimension;
import fons.model.ontology.Filter;
import fons.model.ontology.Filters;
import fons.model.ontology.Match;
import fons.model.ontology.Selection;
import fons.model.ontology.SelectionDefinition;
import fons.model.ontology.SelectionUpdateListener;
import fons.model.ontology.Value;
import fons.model.ontology.ValueSet;
import fons.model.ontology.ValueSpace;
import fons.navigator.util.piccolo.Legend;
import fons.navigator.util.piccolo.PButton;
import fons.navigator.views.island.maps.KMeansMapModel;
import fons.navigator.views.island.maps.NeuralMapModel;

public class VisualIsland extends PNode implements SelectionUpdateListener {
	private static final long serialVersionUID = 1L;
	private static final Logger logger = Logger.getLogger(VisualIsland.class);
	
	private final IslandView view;
	private final Map<ValueSpace, ValueSet> valueSpaces;
	private final List<Island> islands;
	private final MapGrid mapGrid;
	private final MapColorManager colorManager;
	private final MapLegendItemProducer legendItemProducer;
	private final Legend legend;

	private final int resolutionX, resolutionY;
	private double scale;
	private double colorIntensity = 0.25;

	private final MapModel mapModel;

	public VisualIsland(IslandView islandView, 
			List<Set<Dimension>> valueSpaces,
			List<Set<Dimension>> islands,
			int resolutionX, int resolutionY,
			Class<? extends MapModel> mapModelClass,
			boolean log, boolean normalize) {
		
		this.view = islandView;
		this.scale = 1/200.0;

		this.resolutionX = resolutionX;
		this.resolutionY = resolutionY;

		Filter islandFilter = createIslandFilter(islands);
		this.valueSpaces = Maps.newHashMapWithExpectedSize(valueSpaces.size());
		
		for (Set<Dimension> dims: valueSpaces) {
			final ValueSpace valueSpace = ValueSpace.of(dims);
			ValueSet valueSet = new ValueSet(valueSpace.filter(islandFilter));
			if (log) valueSet = valueSet.map(new ValueSet.Log10());
			if (normalize) valueSet = valueSet.map(new ValueSet.Normalizer());

			this.valueSpaces.put(valueSpace, valueSet);
		}

		Collection<? extends Match> islandMatches = Match.cross(islands);
		this.islands = Lists.newArrayListWithCapacity(islandMatches.size());

		for (Match match: islandMatches) {
			Island island = new Island(this, ValueSpace.of(match.getDimensions()));
			for (Entry<ValueSpace, ValueSet> entry: this.valueSpaces.entrySet()) {
				final ValueSpace vsp = entry.getKey();
				final ValueSet vset = entry.getValue();
				final Value value = vset.getOneOrNone(match.getDimensions());
				island.addValue(vsp, vset.getMappedValue(value));
			}
			if (!island.isEmpty()) {
				this.islands.add(island);
				this.addChild(island);
			}
		}
		
		for (Island island: createAverageIslands()) {
			this.islands.add(island);
			this.addChild(island);
		}

		this.colorManager = new MapColorManager(this);
		this.legendItemProducer = new MapLegendItemProducer(this);
		for (ValueSpace vsp: this.valueSpaces.keySet()) {
			legendItemProducer.setColorFor(vsp, colorManager.getColorOf(vsp));
		}

		this.mapModel = createMapModel(mapModelClass);
		this.mapGrid = createMapGrid(this.mapModel);

		this.createButtons();
		this.mapModel.createButtons();
		this.legend = createLegend();
		getCamera().addChild(this.legend);
		
		getView().getContext().getStandardSelection().addSelectionUpdateListener(this);
	}
	
	private Collection<Island> createAverageIslands () {
		final Map<ValueSpace, Double> averages = Maps.newHashMap();
		final Map<ValueSpace, Double> sums = Maps.newHashMap();
		
		for (ValueSpace vsp: getValueSpaces()) {
			double sum = 0;
			int count = 0;
			
			for (Island island: getIslands()) {
				double d = island.getValueOf(vsp);
				if (d == 0.0) continue;
				sum += d;
				count++;
			}
			
			if (count == 0) continue;
			sums.put(vsp, sum);
			averages.put(vsp, sum/count);
		}
		
		final List<Island> averageIslands = Lists.newArrayList();
		
		for (ValueSpace vsp: getValueSpaces()) {
			final Island avgIsland = new Island(this, vsp);
			for (Entry<ValueSpace, Double> entry: averages.entrySet()) {
				avgIsland.addValue(entry.getKey(), entry.getValue());
			}
			
			if (!sums.containsKey(vsp)) continue;
			
			avgIsland.addValue(vsp, 2.0);
			averageIslands.add(avgIsland);
		}
		
		this.averageIslands.addAll(averageIslands);
		return averageIslands;
	}
	
	private List<Island> averageIslands = Lists.newArrayList();
	
	public List<Island> getAverageIslands() {
		return this.averageIslands;
	}
	
	
	private MapModel createMapModel(Class<? extends MapModel> mapModelClass) {
		if (NeuralMapModel.class == mapModelClass) 
			return new NeuralMapModel((int) (10 + Math.sqrt(islands.size())), this.getDimensionality(), 
					500 + 5000/(1 + (int) (0.1*islands.size())), 0.25, this);
		else {
			try {
				Constructor<? extends MapModel> cons = mapModelClass.getConstructor(VisualIsland.class);
				return cons.newInstance(this);
			} catch (Exception e) {
				logger.error("Fehler: " + e.getLocalizedMessage());
				return new KMeansMapModel(this);
			}
		}
	}

	private Legend createLegend() {
		return new Legend(new Color(255, 255, 255, 150), getLegendItemProducer());
	}

	private void createButtons() {
		final PButton labels = new PButton(" Beschriftung ", true);
		view.getVisualCanvas().getSettingButtonsPanel().addButton(labels, 
				new PBasicInputEventHandler() {
			@Override
			public void mouseClicked(PInputEvent event) {
				if (!labels.isPressed()) {
					for (Island island: getIslands()) {
						island.hideLabel();
					}
				} else {
					for (Island island: getIslands()) {
						island.showLabel();
					}
				}
			}
		});

		view.getVisualCanvas().getSettingButtonsPanel().addButton(new PButton(" Mehr Farbe "),
				new PBasicInputEventHandler() {
			@Override
			public void mouseClicked(PInputEvent event) {
				setColorIntensity(getColorIntensity()*0.95);
				getMapModel().updateColorField();
				getMapGrid().updateColors();
			}			
		});

		view.getVisualCanvas().getSettingButtonsPanel().addButton(new PButton(" Weniger Farbe "),
				new PBasicInputEventHandler() {
			@Override
			public void mouseClicked(PInputEvent event) {
				setColorIntensity(getColorIntensity()*1.05);
				getMapModel().updateColorField();
				getMapGrid().updateColors();
			}			
		});

		view.getVisualCanvas().getSettingButtonsPanel().addButton(addCluster,  new PBasicInputEventHandler() {
			public void mouseClicked(PInputEvent event) {
				addCluster();
			}
		});
		
		view.getVisualCanvas().getSettingButtonsPanel().addButton(new PButton(" First Cluster "),
				new PBasicInputEventHandler() {
			public void mouseClicked(PInputEvent event) {
				getView().getContext().getStandardSelection().select(Selection.STANDARD_SELECTION_CATEGORY, 
						clusters.iterator().next().getSelection());
			}
		});
	}

	private MapGrid createMapGrid(MapModel mapModel) {
		final MapGrid mapGrid = new MapGrid(this);
		this.addChild(mapGrid);
		mapGrid.moveToBack();
		return mapGrid;
	}

	private Filter createIslandFilter(List<Set<Dimension>> islands) {
		Filter islandFilter = null;
		for (Set<Dimension> islandPart: islands) {
			if (islandFilter == null) islandFilter = Filters.anyOf(islandPart);
			else {
				islandFilter.and(Filters.anyOf(islandPart));
			}
		}
		return islandFilter;
	}

	@Override
	public void dispose() {
		// TODO Auto-generated method stub

	}

	@Override
	public void beforeSelectionChanged() {
		// TODO Auto-generated method stub

	}

	@Override
	public void afterSelectionChanged() {
		// TODO Auto-generated method stub

	}

	@Override
	public void selectedSelection(SelectionDefinition definition) {
		for (Island island: islands) {
			for (Dimension d: definition.getDimensions())
				if (island.getIslandSpace().getDimensions().contains(d)) {
					island.showLabel();
				}
		}
	}

	public List<Island> getIslands() {
		return Collections.unmodifiableList(islands);
	}

	public MapGrid getMapGrid() {
		return mapGrid;
	}

	public int getResolutionX() {
		return resolutionX;
	}

	public int getResolutionY() {
		return resolutionY;
	}

	public double getValueScale() {
		return scale;
	}

	public PCamera getCamera() {
		return view.getCamera();
	}

	public Collection<ValueSpace> getValueSpaces() {
		return valueSpaces.keySet();
	}

	public IslandView getView() {
		return this.view;
	}

	public void setValueScale(double scale) {
		this.scale = scale;
	}

	public MapColorManager getColorManager() {
		return colorManager;
	}

	public MapLegendItemProducer getLegendItemProducer() {
		return legendItemProducer;
	}

	public Legend getLegend() {
		return legend;
	}

	public MapModel getMapModel() {
		return mapModel;
	}

	public int getDimensionality() {
		return this.valueSpaces.size();
	}


	public double getColorIntensity() {
		return colorIntensity;
	}

	public void setColorIntensity(double colorIntensity) {
		this.colorIntensity = colorIntensity;
	}

	private final List<Cluster> clusters = Lists.newLinkedList();
	private PButton addCluster = new PButton(" Cluster benennen ");
	private Cluster newCluster = null;
	public void newCluster(Cluster cluster) {
		newCluster = cluster;
	}

	public void addCluster() {
		if (newCluster == null) return;
		String name = JOptionPane.showInputDialog("Name des Clusters: ");
		newCluster.setName(name);
		newCluster.defineSelection();
		clusters.add(newCluster);
		newCluster = null;
	}
}
